package chalmers.game.subterranean.model.world;

import chalmers.game.subterranean.event.Bus;
import chalmers.game.subterranean.event.events.world.AddLevelEvent;
import chalmers.game.subterranean.model.world.squares.Square;
import chalmers.game.subterranean.model.world.squares.blocks.actionblocks.Ladder;
import chalmers.game.subterranean.util.BlockType;
import chalmers.game.subterranean.util.BlockType.Action;
import chalmers.game.subterranean.util.Direction;
import chalmers.game.subterranean.util.Position;
import chalmers.game.subterranean.util.Resource;
import chalmers.game.subterranean.util.options.WorldValues;
import org.apache.log4j.Logger;

import java.util.ArrayList;

/**
 *
 * The game world will consist of multiple levels where the first level (level 0) is a top level,
 * where shops will be located. The rest of the world will be generated dirt levels, consisting of
 * different squares that may or may not hold different resources.
 * @author Mikael Friderici
 */
public class GameWorld implements IWorld {

    private static final Logger logger = Logger.getLogger("subLog");
    private ArrayList<DepthLevel> levels;
    private int counter = 0;
    private int resourceNumb = 0;
    // World generation.
    private final int RESOURCE_QUOTA = WorldValues.MAX_NUM_LEVELS
        / BlockType.Resource.values().length;
    private ArrayList<BlockType.Resource> resourcePool;
    private BlockType.Resource currentResource;

    public GameWorld() {
        levels = new ArrayList<>();
        resourcePool = new ArrayList<>();
    }

    /**
     * This is needed if the control is going to call methods directly in Player
     */
    @Override
    public void start() {
        generateNewTopLevel();
        generateNewDirtLevel();
        generateNewDirtLevel();
    }

    private void worldGeneration() {

    }

    /**
     * Try to dig in desired direction.
     *
     * @param direction
     * @param position
     * @param damage
     * @return resource from the square.
     */
    @Override
    public Resource dig(Direction direction, Position position, double damage) {
        logger.info("digging " + direction.toString());
        return this.levels.get(position.depth).dig(direction, position, damage);
    }

    /**
     * Return the BlockType that is in the specified position.
     *
     * @param position
     * @return BlockType
     */
    @Override
    public BlockType getBlockType(Position position) {
        return levels.get(position.depth).getBlockType(position);
    }

    /**
     * Try to set specified position square to provided actionBlock.
     *
     * @param position
     * @param actionBlock
     * @return try if success otherwise false.
     */
    @Override
    public boolean addActionBlock(Position position, Square actionBlock) {
        return levels.get(position.depth).addActionBlock(position, actionBlock);
    }

    @Override
    public Action performAction(Position position) {
        return Action.NOTHING;
    }

    /**
     * Try to get next position in desired direction.
     *
     * @param position
     * @param direction
     * @return the next coordinate in the desired direction, if it's possible to move there. Otherwise
     * the present coordinate will be returned.
     */
    @Override
    public Position nextPosition(Position position, Direction direction) {
        int depth = position.depth;
        Position newPos = levels.get(depth).getPoint(position, direction);

        return newPos;
    }

    /**
     * Try to preform use on specified position square. This will only be done if the block is a
     * ActionBlock.
     *
     * @param position
     */
    @Override
    public void performUse(Position position) {
        levels.get(position.depth).performUse(position);
    }

    /**
     * Try to raise a ladder of specified height starting at position.
     *
     * @param startPos
     * @param height
     * @return number of ladder blocks not used.
     */
    @Override
    public int raiseLadder(Position startPos, int height) {
        int currentY = startPos.point.y;
        int depth = startPos.depth;
        int x = startPos.point.x;
        logger.info("raising ladder at " + startPos.toString() + "height= "
            + new Integer(height).toString());
        for (int i = 0; i < height; i++) {
            if (currentY < 0) {
                if (depth == 0) {
                    return i;
                }
                depth--;
                currentY = WorldValues.LEVEL_HEIGHT;
            }
            if (!(levels.get(depth).addActionBlock(new Position(depth, x, currentY),
                new Square(new Ladder())))) {
                return i;
            }
            currentY--;
        }
        return 0;
    }

    /**
     * Retrieve specified DepthLevel.
     *
     * @param level depth.
     * @return DepthLevel
     */
    public DepthLevel getDepthLevel(int depth) {
        if (depth >= 0 && depth < levels.size())
            return levels.get(depth);
        return null;
    }

    /**
     * Try to add a ladder block on specified position, if the block is already a ladder block
     * a ladder block will be added to the position north of the current.
     *
     * @param position
     * @return true on success otherwise false.
     */
    @Override
    public boolean addLadderBlock(Position position) {
        BlockType type = getBlockType(position);
        if (isEmptyBlock(position)) {
            Square ladderBlock = new Square(new Ladder());
            levels.get(position.depth).addActionBlock(position, ladderBlock);
            return true;
        } else if (type.action.equals(BlockType.Action.LADDER)) {
            Position pos = nextPosition(position, Direction.NORTH);
            if (!pos.equals(position)) {
                return addLadderBlock(pos);
            }
        }
        return false;
    }


    private boolean isEmptyBlock(Position position) {
        BlockType type = getBlockType(position);
        return type.dig.equals(BlockType.Dig.AIR);
    }

    protected void generateNewDirtLevel() {
        BlockType.Resource[] resources = BlockType.Resource.values();
        if (counter == RESOURCE_QUOTA) {
            counter = 0;
            if (resourceNumb < BlockType.Resource.values().length - 1) {
                resourcePool.add(resources[resourceNumb]);
                resourceNumb++;
            }
        }
        counter++;
        currentResource = resources[resourceNumb];

        if (!currentResource.equals(BlockType.Resource.NULL)) {
            DirtLevel newLevel = new DirtLevel(this, currentResource, resourcePool);
            levels.add(newLevel);
            Bus.INSTANCE.post(new AddLevelEvent(newLevel.getLevelBlocks(), getNumOfLevels() - 1));
        }
    }

    protected void generateNewTopLevel() {
        TopLevel newLevel = new TopLevel(this);
        levels.add(newLevel);
        Bus.INSTANCE.post(new AddLevelEvent(newLevel.getLevelBlocks(), getNumOfLevels() - 1));
    }

    /**
     * Retrieve total number of levels.
     *
     * @return total number of levels.
     */
    public int getNumOfLevels() {
        return levels.size();
    }

    @Override
    public IWorld restart() {
        levels = new ArrayList<>();
        resourcePool = new ArrayList<>();
        this.start();
        return this;
    }
}
